package owg.util.opengl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import owg.util.Calc;


/**
 * This class loads material files and keeps an index. 
 * GFX handles the textures, they're not here.
 */
public class Mtllib
	{
	/**A set of mtllibs mapped on mtllib names*/
	private static HashMap<String,Mtllib> mtllibsSet;
	private static ArrayList<Mtllib> mtllibsList;
	
	static
		{
		mtllibsSet = new HashMap<String,Mtllib>();
		mtllibsList = new ArrayList<Mtllib>();
		loadDir("res/materials");
		}
	
	private static void loadDir(String dirName) 
		{
		File dir = new File(dirName);
		System.out.print("Load Material dir: "+dirName+" - ");
		if(dir.isDirectory()) 
			{
			String filenames[] = dir.list();
			for (String file : filenames)
				{
				if(file.endsWith(".mtl"))
					loadLib(dirName,file);
				}
			}
		System.out.println("... done.");
		}
	
	private static void loadLib(String dirName, String name)
		{
		//load and insert into HashMap
		System.out.print(name + " ");
		File file = new File(dirName+"/"+name);
		FileReader fr = null;
		BufferedReader br = null;
		try//Setup readers
			{
			fr = new FileReader(file);
			br = new BufferedReader(fr);
			
			//Create list(0-indexed)
			Mtllib list = new Mtllib(name);
			
			//Put list in HashMap
			mtllibsSet.put(name, list);
			mtllibsList.add(list);
			
			//Start with nulls
			int materialIndex=0;
			boolean textureOverride = true;
			boolean transparentFaces = false;
			boolean doubleSided = false;
			Material mtl = null;
			
			String line;
			while((line=br.readLine())!=null)
				{
				if(line.contains("@ENABLECOLORTEXTURE"))
					textureOverride = line.endsWith("OFF");
				else if(line.contains("@TRANSPARENTFACES"))
					transparentFaces = !line.endsWith("OFF");
				else if(line.contains("@DOUBLESIDED"))
					doubleSided = !line.endsWith("OFF");
				else if (line.startsWith("newmtl"))
					{
					line = strip(line,6);
					materialIndex++;
					mtl = new Material(name,line,materialIndex);
					mtl.textureOverride = textureOverride;
					mtl.transparentFaces = transparentFaces;
					mtl.doubleSided = doubleSided;
					list.add(mtl);
					}
				else if (line.startsWith("map_Ks"))
					{
					mtl.specularTexture = true;
					}
				else if (line.startsWith("map_Kd"))
					{
					line = strip(line,6);
					int lastDot = line.lastIndexOf('.');
					if(lastDot!=-1)
						line = line.substring(0,lastDot);
					if(line.endsWith("_pony"))
						line = line.substring(0, line.length()-5);
					mtl.diffuseTex = TextureLib.tex(line);
					}
				else if (line.startsWith("Ka"))
					{
					line = strip(line,2);
					mtl.ambientColor = new ColorF(Calc.getFloatArray(line));
					mtl.setAlpha(mtl.alpha);
					}
				else if (line.startsWith("Kd"))
					{
					line = strip(line,2);
					mtl.diffuseColor = new ColorF(Calc.getFloatArray(line));
					mtl.setAlpha(mtl.alpha);
					}
				else if (line.startsWith("Ks"))
					{
					line = strip(line,2);
					mtl.specularColor = new ColorF(Calc.getFloatArray(line));
					mtl.setAlpha(mtl.alpha);
					}
				else if (line.startsWith("Ke"))
					{
					line = strip(line,2);
					mtl.emitColor = new ColorF(Calc.getFloatArray(line));
					mtl.setAlpha(mtl.alpha);
					}
				else if (line.startsWith("d"))
					{
					line = strip(line,1);
					float a = Float.parseFloat(line);
					mtl.setAlpha(a);
					}
				else if (line.startsWith("Tr"))
					{
					line = strip(line,1);
					float a = Float.parseFloat(line);
					mtl.setAlpha(a);
					}
				}
			}
		catch (IOException e)//Print info for debugging if anything fails
			{
			System.err.println("Material loader fail: "+file);
			e.printStackTrace();
			}
		catch (NumberFormatException e)
			{
			System.err.println("Material loader fail: "+file);
			e.printStackTrace();
			}
		finally//Close readers if they were opened at all
			{
			try {br.close();
				fr.close();}
			catch (Exception e){}
			}
		}

	/**
	 * Strips .obj comments and whitespaces from a string and the first letters
	 * @param line the string to modify
	 * @param start the number of letters to strip from the start
	 */
	public static String strip(String line,int start)
		{
		line = line.substring(start);//Strip away the indicated start
		int commentIndex = line.indexOf("#");//Find remaining comment, if any
		if (commentIndex!=-1)
			{
			line = line.substring(0, commentIndex);
			}
		return line.trim();//Trim away whitespaces
		}
	
	/**
	 * Get a material from a loaded mtllib. 
	 * @param mtllib the file name of the mtllib(including extension)
	 * @param index the index of the material in the lib(1-indexed!)
	 */
	public static Material getMaterialOneIndexed(String mtllib, int index)
		{
		return mtllibsSet.get(mtllib).get(index-1);
		}

	public static Material getMaterial(String mtllib, String mtlName)
		{
		Mtllib lib = mtllibsSet.get(mtllib);
		if(lib == null)
			return null;
		else
			return lib.get(mtlName);
		}

	public static String[] listMTLLibs()
		{
		String[] list = new String[mtllibsList.size()];
		for(int i = 0; i<list.length; i++)
			list[i] = mtllibsList.get(i).name;
		return list;
		}

	public static ArrayList<Material> getMaterials(String mtllib)
		{
		Mtllib mtl = mtllibsSet.get(mtllib);
		if(mtl == null)
			return null;
		else
			return mtl.materialList;		
		}
	
	/**Returns whether the material library with the given name has been loaded.*/
	public static boolean exists(String mtllib)
		{
		return mtllibsSet.containsKey(mtllib);
		}
	
	/**
	 * Returns whether the two material libraries are compatible, e.g. if a Model using the one will be able to use the other
	 * without generating null pointer-related errors.<br/>
	 * <br/>
	 * Returns true if both materials are null, and always false if only one is null.
	 * Always returns false if one (or both) of the mtllibs do not exist.
	 * @param mtllib0 The name of one mtllib
	 * @param mtllib1 The name of another mtllib
	 * @return Whether the two mtllibs contain materials with the same names
	 */
	public static boolean matches(String mtllib0, String mtllib1)
		{
		if(mtllib0 == null)
			return mtllib1 == null;
		Mtllib boy = mtllibsSet.get(mtllib0);
		if(boy == null)
			return false;
		Mtllib girl = mtllibsSet.get(mtllib1);
		return boy.matches(girl);
		}
	/**Returns whether the mtllib with the given name can be used with the given list of material names.
	 * If the mtllib is null, then false is returned. If the material list is null, then true is returned.*/
	public static boolean isCompatible(String mtllib, ArrayList<String> materials)
		{
		if(mtllib == null)
			return false;
		return mtllibsSet.get(mtllib).contains(materials);
		}
	/**Returns the name of the first loaded mtllib which contains materials with all the given names.
	 * Will be null if no such thing exists.*/
	public static String getFirstCompatibleMtllib(ArrayList<String> materialNames)
		{
		for(Mtllib lib : mtllibsList)
			{
			if(lib.contains(materialNames))
				return lib.name;
			}
		return null;
		}
	
	
	String name;
	ArrayList<Material> materialList;
	HashMap<String, Material> materialSet;
	
	public Mtllib(String name)
		{
		this.name = name;
		materialList = new ArrayList<Material>();
		materialSet = new HashMap<String, Material>();
		}
	
	protected void add(Material mat)
		{
		materialList.add(mat);
		materialSet.put(mat.name, mat);
		}

	/**Returns whether this mtllib is an exact match of the other mtllib. 
	 * Note that the material order must also match.
	 * If other is null, then false is returned.*/
	protected boolean matches(Mtllib other)
		{
		if(other == null)
			return false;
		
		if(materialList.size() != other.materialList.size())
			return false;
		for(int j = 0; j<materialList.size(); j++)
			{
			if(!materialList.get(j).name.equals(other.materialList.get(j).name))
				return false;
			}
		return true;
		}
	/**Returns whether this mtllib contains all the materials from the list of material names.
	 * If other is null, then true is returned.*/
	protected boolean contains(ArrayList<String> other)
		{
		if(other == null)
			return true;
		
		for(int j = 0; j<other.size(); j++)
			{
			if(get(other.get(j)) == null)
				return false;
			}
		return true;
		}
	
	protected Material get(String mtlName)
		{
		return materialSet.get(mtlName);
		}
	
	protected Material get(int index)
		{
		return materialList.get(index);
		}
	
	@Override
	public String toString()
		{
		return name;
		}
	}
